実践!AWS CDK #23 RDS インスタンス
はじめに
今回は最後のリソース、RDS の DB インスタンスを作成します。
前回作成した DB クラスターに紐付けます。
前回の記事はこちら。
AWS 構成図
今回でこの構成が完成します。
2 台の DB インスタンスを立ち上げ、そのうち 1 台が Aurora レプリカとなります。
設計
プロパティは以下の通り。
devio-stg-rds-instance-1a
項目 | 値 |
---|---|
Availability Zone | ap-northeast-1a |
エンジン | Aurora MySQL |
DB インスタンスクラス | db.r5.large |
DB クラスター ID | devio-stg-rds-cluster |
サブネットグループ | devio-stg-rds-sng |
パラメータグループ | 前々回作ったやつ |
モニタリング詳細度 | 60 秒 |
モニタリングロール | devio-stg-role-rds |
パフォーマンスインサイト | 有効 |
パフォーマンスインサイト保持期間 | 7 日 |
マイナーバージョン自動アップグレード | 無効 |
メンテナンスウィンドウ | 月曜 5:00-5:30 JST(日曜 20:00-20:30 UTC) |
devio-stg-rds-instance-1c
項目 | 値 |
---|---|
Availability Zone | ap-northeast-1c |
エンジン | Aurora MySQL |
DB インスタンスクラス | db.r5.large |
DB クラスター ID | devio-stg-rds-cluster |
サブネットグループ | devio-stg-rds-sng |
パラメータグループ | 前々回作ったやつ |
モニタリング詳細度 | 60 秒 |
モニタリングロール | devio-stg-role-rds |
パフォーマンスインサイト | 有効 |
パフォーマンスインサイト保持期間 | 7 日 |
マイナーバージョン自動アップグレード | 無効 |
メンテナンスウィンドウ | 月曜 5:30-6:00 JST(日曜 20:30-21:00 UTC) |
2 つのインスタンスで異なる部分は AZ とメンテナンスウィンドウくらいです。
メンテナンスウィンドウに関してはクラスターとインスタンスそれぞれで 30 分ずつ時間をずらしました。
実装
RDS に関する処理を行うクラスに、ハイライト部分を追記しました。
import * as cdk from '@aws-cdk/core'; import { CfnDBSubnetGroup, CfnDBClusterParameterGroup, CfnDBParameterGroup, CfnDBCluster, CfnDBInstance } from '@aws-cdk/aws-rds'; import { CfnSubnet, CfnSecurityGroup } from '@aws-cdk/aws-ec2'; import { CfnSecret } from '@aws-cdk/aws-secretsmanager'; import { CfnRole } from '@aws-cdk/aws-iam'; import { Resource } from './abstract/resource'; import { SecretsManager, OSecretKey } from './secretsManager'; interface InstanceInfo { readonly id: string; readonly availabilityZone: string; readonly preferredMaintenanceWindow: string; readonly resourceName: string; readonly assign: (instance: CfnDBInstance) => void; } export class Rds extends Resource { public dbCluster: CfnDBCluster; public dbInstance1a: CfnDBInstance; public dbInstance1c: CfnDBInstance; private static readonly engine = 'aurora-mysql'; private static readonly databaseName = 'devio'; private static readonly dbInstanceClass = 'db.r5.large'; private readonly subnetDb1a: CfnSubnet; private readonly subnetDb1c: CfnSubnet; private readonly securityGroupRds: CfnSecurityGroup; private readonly secretRdsCluster: CfnSecret; private readonly iamRoleRds: CfnRole; private readonly instances: InstanceInfo[] = [ { id: 'RdsDbInstance1a', availabilityZone: 'ap-northeast-1a', preferredMaintenanceWindow: 'sun:20:00-sun:20:30', resourceName: 'rds-instance-1a', assign: instance => this.dbInstance1a = instance }, { id: 'RdsDbInstance1c', availabilityZone: 'ap-northeast-1c', preferredMaintenanceWindow: 'sun:20:30-sun:21:00', resourceName: 'rds-instance-1c', assign: instance => this.dbInstance1c = instance } ]; constructor( subnetDb1a: CfnSubnet, subnetDb1c: CfnSubnet, securityGroupRds: CfnSecurityGroup, secretRdsCluster: CfnSecret, iamRoleRds: CfnRole ) { super(); this.subnetDb1a = subnetDb1a; this.subnetDb1c = subnetDb1c; this.securityGroupRds = securityGroupRds; this.secretRdsCluster = secretRdsCluster; this.iamRoleRds = iamRoleRds; }; createResources(scope: cdk.Construct) { const subnetGroup = this.createSubnetGroup(scope); const clusterParameterGroup = this.createClusterParameterGroup(scope); const parameterGroup = this.createParameterGroup(scope); this.dbCluster = this.createCluster(scope, subnetGroup, clusterParameterGroup); for (const instanceInfo of this.instances) { const instance = this.createInstance(scope, instanceInfo, this.dbCluster, subnetGroup, parameterGroup); instanceInfo.assign(instance); } } private createSubnetGroup(scope: cdk.Construct): CfnDBSubnetGroup { const subnetGroup = new CfnDBSubnetGroup(scope, 'RdsDbSubnetGroup', { dbSubnetGroupDescription: 'Subnet Group for RDS', subnetIds: [this.subnetDb1a.ref, this.subnetDb1c.ref], dbSubnetGroupName: this.createResourceName(scope, 'rds-sng') }); return subnetGroup; } private createClusterParameterGroup(scope: cdk.Construct): CfnDBClusterParameterGroup { const clusterParameterGroup = new CfnDBClusterParameterGroup(scope, 'RdsDbClusterParameterGroup', { description: 'Cluster Parameter Group for RDS', family: 'aurora-mysql5.7', parameters: { time_zone: 'UTC' } }); return clusterParameterGroup; } private createParameterGroup(scope: cdk.Construct): CfnDBParameterGroup { const parameterGroup = new CfnDBParameterGroup(scope, 'RdsDbParameterGroup', { description: 'Parameter Group for RDS', family: 'aurora-mysql5.7' }); return parameterGroup; } private createCluster(scope: cdk.Construct, subnetGroup: CfnDBSubnetGroup, clusterParameterGroup: CfnDBClusterParameterGroup): CfnDBCluster { const cluster = new CfnDBCluster(scope, 'RdsDbCluster', { engine: Rds.engine, backupRetentionPeriod: 7, databaseName: Rds.databaseName, dbClusterIdentifier: this.createResourceName(scope, 'rds-cluster'), dbClusterParameterGroupName: clusterParameterGroup.ref, dbSubnetGroupName: subnetGroup.ref, enableCloudwatchLogsExports: ['error'], engineMode: 'provisioned', engineVersion: '5.7.mysql_aurora.2.10.0', masterUserPassword: SecretsManager.getDynamicReference(this.secretRdsCluster, OSecretKey.MasterUserPassword), masterUsername: SecretsManager.getDynamicReference(this.secretRdsCluster, OSecretKey.MasterUsername), port: 3306, preferredBackupWindow: '19:00-19:30', preferredMaintenanceWindow: 'sun:19:30-sun:20:00', storageEncrypted: true, vpcSecurityGroupIds: [this.securityGroupRds.attrGroupId] }); return cluster; } private createInstance(scope: cdk.Construct, instanceInfo: InstanceInfo, cluster: CfnDBCluster, subnetGroup: CfnDBSubnetGroup, parameterGroup: CfnDBParameterGroup): CfnDBInstance { const instance = new CfnDBInstance(scope, instanceInfo.id, { dbInstanceClass: Rds.dbInstanceClass, autoMinorVersionUpgrade: false, availabilityZone: instanceInfo.availabilityZone, dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: this.createResourceName(scope, instanceInfo.resourceName), dbParameterGroupName: parameterGroup.ref, dbSubnetGroupName: subnetGroup.ref, enablePerformanceInsights: true, engine: Rds.engine, monitoringInterval: 60, monitoringRoleArn: this.iamRoleRds.attrArn, performanceInsightsRetentionPeriod: 7, preferredMaintenanceWindow: instanceInfo.preferredMaintenanceWindow, }); return instance; } }
特に複雑なことはしておらず。いつものようにリソースを作成するためのメソッドを追加しています。
メインのプログラムはこちら。
ハイライト部分を追記しました。
~ 省略 ~ export class DevioStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); ~ 省略 ~ // RDS const rds = new Rds( subnet.db1a, subnet.db1c, securityGroup.rds, secretsManager.secretRdsCluster, iamRole.rds ); rds.createResources(this); } }
モニタリングロールを Rds クラスに渡すためにコンストラクタにパラメータを追加しました。
テスト
テストコードはこちら。
ハイライト部分を追記しました。
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as Devio from '../../lib/devio-stack'; test('Rds', () => { const app = new cdk.App(); const stack = new Devio.DevioStack(app, 'DevioStack'); expect(stack).to(countResources('AWS::RDS::DBSubnetGroup', 1)); expect(stack).to(haveResource('AWS::RDS::DBSubnetGroup', { DBSubnetGroupDescription: 'Subnet Group for RDS', SubnetIds: anything(), DBSubnetGroupName: 'undefined-undefined-rds-sng' })); expect(stack).to(countResources('AWS::RDS::DBClusterParameterGroup', 1)); expect(stack).to(haveResource('AWS::RDS::DBClusterParameterGroup', { Description: 'Cluster Parameter Group for RDS', Family: 'aurora-mysql5.7', Parameters: { time_zone: 'UTC' } })); expect(stack).to(countResources('AWS::RDS::DBParameterGroup', 1)); expect(stack).to(haveResource('AWS::RDS::DBParameterGroup', { Description: 'Parameter Group for RDS', Family: 'aurora-mysql5.7' })); expect(stack).to(countResources('AWS::RDS::DBCluster', 1)); expect(stack).to(haveResource('AWS::RDS::DBCluster', { Engine: 'aurora-mysql', BackupRetentionPeriod: 7, DatabaseName: 'devio', DBClusterIdentifier: 'undefined-undefined-rds-cluster', DBClusterParameterGroupName: anything(), DBSubnetGroupName: anything(), EnableCloudwatchLogsExports: ['error'], EngineMode: 'provisioned', EngineVersion: '5.7.mysql_aurora.2.10.0', MasterUsername: anything(), MasterUserPassword: anything(), Port: 3306, PreferredBackupWindow: '19:00-19:30', PreferredMaintenanceWindow: 'sun:19:30-sun:20:00', StorageEncrypted: true, VpcSecurityGroupIds: anything() })); expect(stack).to(countResources('AWS::RDS::DBInstance', 2)); expect(stack).to(haveResource('AWS::RDS::DBInstance', { DBInstanceClass: 'db.r5.large', AutoMinorVersionUpgrade: false, AvailabilityZone: 'ap-northeast-1a', DBClusterIdentifier: anything(), DBInstanceIdentifier: 'undefined-undefined-rds-instance-1a', DBParameterGroupName: anything(), DBSubnetGroupName: anything(), EnablePerformanceInsights: true, Engine: 'aurora-mysql', MonitoringInterval: 60, MonitoringRoleArn: anything(), PerformanceInsightsRetentionPeriod: 7, PreferredMaintenanceWindow: 'sun:20:00-sun:20:30', })); expect(stack).to(haveResource('AWS::RDS::DBInstance', { DBInstanceClass: 'db.r5.large', AutoMinorVersionUpgrade: false, AvailabilityZone: 'ap-northeast-1c', DBClusterIdentifier: anything(), DBInstanceIdentifier: 'undefined-undefined-rds-instance-1c', DBParameterGroupName: anything(), DBSubnetGroupName: anything(), EnablePerformanceInsights: true, Engine: 'aurora-mysql', MonitoringInterval: 60, MonitoringRoleArn: anything(), PerformanceInsightsRetentionPeriod: 7, PreferredMaintenanceWindow: 'sun:20:30-sun:21:00', })); });
以下を確認しています。
- DB インスタンスのリソースが 2 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。
前回作成したクラスター配下に、今回作成したインスタンス 2 台が紐付いています。
前回は 作成中
だった各エンドポイントのステータスも 利用可能
に。
パフォーマンスインサイトも確認可能です。
続いて(少し長くなりますが)EC2 インスタンスからの疎通確認を行います。
MySQL クライアントのインストール
SSM のセッションマネージャーから EC2 にログインし、公式のインストール手順 を参考に MySQL のクライアントをインストールします。
以下のコマンドを実行し、MySQL の yum リポジトリをシステムのリポジトリリストに追加します。
$ sudo yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
次に現在設定されているサブリポジトリのバージョンを確認します。
$ yum repolist all | grep mysql
現状バージョン 8.0
が有効化されているので、こちらを無効化し 5.7
を有効化します。
$ sudo yum-config-manager --disable mysql80-community $ sudo yum-config-manager --enable mysql57-community
再度ステータスの確認。
$ yum repolist all | grep mysql
MySQL クライアントをインストールします。
$ sudo yum -y install mysql-community-client
インストールできました。
ここまでの MySQL クライアントインストール処理も EC2 の UserData に追加しておきます。
#!/bin/bash # Apache のインストール sudo yum -y install httpd sudo systemctl enable httpd sudo systemctl start httpd # MySQL クライアントのインストール sudo yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm sudo yum-config-manager --disable mysql80-community sudo yum-config-manager --enable mysql57-community sudo yum -y install mysql-community-client
RDS インスタンスへの接続
クラスター(ライター)エンドポイントをターゲットに以下のコマンドを実行します。(ここでのクラスターエンドポイントは devio-stg-rds-cluster.cluster-cndvsdcsdzja.ap-northeast-1.rds.amazonaws.com
)
パスワードは Secrets Manager に保存されているものを入力します。
$ mysql -h devio-stg-rds-cluster.cluster-cndvsdcsdzja.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p
接続できました!
クラスター作成時に指定した DB devio
も作成されています。
DB devio
に user
テーブルを作成してみます。
$ create table devio.user (id int, name varchar(8));
テーブルの作成(書き込み)も OK ですね。
なお RDS への接続時にリーダーエンドポイントを指定した場合、書き込み処理は行なえません。その名の通り、書き込み処理を行う場合はクラスター(ライター)エンドポイントを、読み込み処理のみを行う場合はリーダーエンドポイントを指定するようにしましょう。(ここでのリーダーエンドポイントは devio-stg-rds-cluster.cluster-ro-cndvsdcsdzja.ap-northeast-1.rds.amazonaws.com
)
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
RdsDbInstance1a: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.r5.large AutoMinorVersionUpgrade: false AvailabilityZone: ap-northeast-1a DBClusterIdentifier: Ref: RdsDbCluster DBInstanceIdentifier: devio-stg-rds-instance-1a DBParameterGroupName: Ref: RdsDbParameterGroup DBSubnetGroupName: Ref: RdsDbSubnetGroup EnablePerformanceInsights: true Engine: aurora-mysql MonitoringInterval: 60 MonitoringRoleArn: Fn::GetAtt: - RoleRds - Arn PerformanceInsightsRetentionPeriod: 7 PreferredMaintenanceWindow: sun:20:00-sun:20:30 RdsDbInstance1c: Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.r5.large AutoMinorVersionUpgrade: false AvailabilityZone: ap-northeast-1c DBClusterIdentifier: Ref: RdsDbCluster DBInstanceIdentifier: devio-stg-rds-instance-1c DBParameterGroupName: Ref: RdsDbParameterGroup DBSubnetGroupName: Ref: RdsDbSubnetGroup EnablePerformanceInsights: true Engine: aurora-mysql MonitoringInterval: 60 MonitoringRoleArn: Fn::GetAtt: - RoleRds - Arn PerformanceInsightsRetentionPeriod: 7 PreferredMaintenanceWindow: sun:20:30-sun:21:00
GitHub
今回のソースコードは コチラ です。
おわりに
今回で当初目標としていた構成を AWS CDK ですべて実装することができました!
プログラムを書きながら学べたことがとても多く、非常に充実した取り組みでした。
本シリーズはこの回をもって 一部完 とさせていただきます。
以降はもう少しのんびりとした周期で、構成の拡張
や AWS CDK の小ワザ
などをお伝えしていきたいと思います。
ありがとうございました。
リンク
- class CfnDBInstance (construct) | AWS CDK API Reference
- AWS::RDS::DBInstance | AWS CloudFormation User Guide
- DB インスタンスクラス | Amazon Aurora User Guide
- Amazon Aurora でのレプリケーション | Amazon Aurora User Guide
- MySQL データベースエンジンを実行している DB インスタンスへの接続 | Amazon RDS User Guide
- Installing MySQL on Linux Using the MySQL Yum Repository | MySQL 8.0 Reference Manual
- MySQL Yum Repository | MySQL Community Downloads
- Amazon Linux2にMySQLクライアントを4バージョンいれてみた。Ansible Playbookを添えて | DevelopersIO